<?php

namespace App\Services;

use App\Exceptions\CustomException;
use App\Models\Hall;
use App\Models\HallConfig;
use common\enum\MoneyTransferEnum;
use common\utils\Pack;
use Illuminate\Support\Facades\Log;
use App\Models\HallPartnerConfig;
use App\Models\Money;
use App\Models\MoneyTransfer;
use App\Models\MoneyLog;
use App\Models\MoneyPartner;
use App\Models\MoneyPartnerLog;
use App\Models\MoneyTransferLog;
use App\Models\PartnerUser;
use Illuminate\Support\Facades\DB;
use App\Models\User;
use common\enum\MoneyExchangeEnum;
use common\enum\MoneyLogEnum;
use common\utils\SysCode;
use common\enum\CommonEnum;
use Illuminate\Support\Facades\Redis;
use common\utils\RedisKeys;

class MoneyService
{
    /**
     * 更新钱包名记录日志  捕捉异常处理
     * @param $hallId 厅主ID
     * @param $userId 用户ID
     * @param $username 用户名
     * @param $orderNumber 订单号
     * @param $category 日志大类
     * @param $type 日志小类
     * @param $amount 交易金额，有正负
     * @param $couponAmount 优惠金额，有正负
     * @param $feeAmount 费用金额，有正负
     * @param $createAdminId 操作人id
     * @param $remark 备注
     * @param $transaction 是否开启事务  默认开启
     */
    public static function updateMoney(
        $hallId,
        $userId,
        $username,
        $orderNumber,
        $category,
        $type,
        $amount,
        $couponAmount,
        $feeAmount,
        $createAdminId,
        $remark,
        $transaction = true
    ) {
        $transaction && DB::beginTransaction();
        try {
            $freezeExchangeAmount = 0;
            $freezeWithdrawAmount = 0;
            $errorCode = SysCode::BANK_EXCHANGE_SQL_ERROR;
            switch ($type) {
                case MoneyLogEnum::USER_TRANSFER_MTOP_LOCK: // 'name' => '主转子钱包,锁定金额',
                    $freezeExchangeAmount = -$amount;
                    $remark .= ":" . $freezeExchangeAmount / CommonEnum::MONEY_RATE;
                    $errorCode = SysCode::BANK_EXCHANGE_MONEY_FREEZE;
                    break;
                case MoneyLogEnum::USER_TRANSFER_MTOP_UNLOCK: // 'name' => '主转子钱包,解锁金额',
                    $freezeExchangeAmount = $amount;
                    $amount = 0;
                    $remark .= ":" . $freezeExchangeAmount / CommonEnum::MONEY_RATE;
                    $errorCode = SysCode::BANK_EXCHANGE_MONEY_UNFREEZE;
                    break;
                case MoneyLogEnum::USER_TRANSFER_MTOP_LOCK_REVERT: // 'name' => '主转子钱包,转账失败,解锁金额',
                    $freezeExchangeAmount = -$amount;
                    $remark .= ":" . $freezeExchangeAmount / CommonEnum::MONEY_RATE;
                    $errorCode = SysCode::BANK_EXCHANGE_MONEY_UNFREEZE;
                    break;
                case MoneyLogEnum::USER_WITHDROW_WITHDRAW: // 'name' => '会员提款',
                case MoneyLogEnum::USER_WITHDROW_WITHDRAW_MANUAL: // 'name' => '手动提款',
                    $freezeWithdrawAmount = -$amount;
                    $remark .= ":" . $freezeWithdrawAmount / CommonEnum::MONEY_RATE;
                    break;
                case MoneyLogEnum::USER_CASH_WITHDRAWAL_PAYED: // 'name' => '会员手动提现-提现付款（财务打款成功）',
                case MoneyLogEnum::USER_CASH_OFFLINE_WITHDRAWAL_PAYED: // 'name' => '厅主后台人工提现-提现付款（财务打款成功）',
                    $freezeWithdrawAmount = -$amount;
                    $amount = 0;
                    $remark .= ":" . $freezeWithdrawAmount / CommonEnum::MONEY_RATE;
                    break;
            }
            //查询当前余额
            $addAmount = $amount + $couponAmount + $feeAmount;
            $ret = DB::update("UPDATE money SET 
                balance = balance + {$addAmount},
                freeze_exchange = freeze_exchange + {$freezeExchangeAmount},
                freeze_withdraw = freeze_withdraw + {$freezeWithdrawAmount}
                WHERE 
                    user_id = {$userId}
                    AND balance + {$addAmount} >= 0
                    AND freeze_exchange + {$freezeExchangeAmount} >= 0
                    AND freeze_withdraw + {$freezeWithdrawAmount} >= 0
            ");
            //操作失败
//            if(!$ret && !empty($addAmount)){
//                $transaction && DB::beginTransaction();
//                error_exit($errorCode);
//            }

            // add log
            $money = Money::where(['user_id' => $userId])->first();
            $log = [
                'hall_id' => $hallId,
                'user_id' => $userId,
                'username' => $username,
                'order_number' => $orderNumber,
                'category' => $category,
                'type' => $type,
                'amount' => $amount,
                'balance' => $money['balance'],
                'balance_before' => $money['balance'] - $addAmount,
                'coupon_amount' => $couponAmount,
                'fee_amount' => $feeAmount,
                'freeze_exchange' => $freezeExchangeAmount,
                'freeze_withdraw' => $freezeWithdrawAmount,
                'create_admin_id' => $createAdminId,
                'remark' => $remark,
            ];
            MoneyLog::insert($log);
            $transaction && DB::commit();
        } catch (\Exception $e) {
            $transaction && DB::rollBack();
            Log::error(sprintf("金额处理失败: userId:%s, username:%s, orderNumber:%s, amount:%s, msg:%s", $userId, $username,
                $orderNumber, $amount, $e->getTraceAsString()));
            error_exit($errorCode);
        }
    }

    /**
     * 转账成功 money_log
     */
    public static function addMoneyLog(
        $hallId,
        $userId,
        $username,
        $orderNumber,
        $category,
        $type,
        $amount,
        $balance,
        $beforeBalance,
        $createAdminId,
        $remark
    ) {
        $log = [
            'hall_id' => $hallId,
            'user_id' => $userId,
            'username' => $username,
            'order_number' => $orderNumber ?? '',
            'category' => $category,
            'type' => $type,
            'amount' => $amount,
            'balance' => $balance,
            'balance_before' => $beforeBalance,
            'create_admin_id' => $createAdminId ?? 0,
            'remark' => $remark,
        ];
        if (!MoneyLog::insert($log)) {
            Log::error(sprintf("金额处理失败 addLog: userId:%s, username:%s, orderNumber:%s, amount:%s, msg:%s", $userId,
                $username, $orderNumber, $amount, $e->getTraceAsString()));
            return false;
        }
        return true;
    }

    public static function getTotal($userId)
    {
        $moneyModel = Money::where(['user_id' => $userId])->first();
        $partnerMoneyModel = MoneyPartner::where(['user_id' => $userId])->select([
            'user_id',
            DB::raw('sum(balance) as balance')
        ])->first();
        $total = $moneyModel['balance'] + $moneyModel['freeze_exchange'] + $partnerMoneyModel['balance'];
        return $total;
    }

    //额度转化接口
    public static function exchange($params)
    {
        // for test
        // $userId = $params['user_id'];
        // $userModel = $user = User::where(['id' => $userId])->first();
        // $total = null;
        // if (in_array($userModel['username'], ['day001','ace200', 'ace201', 'ace202', 'ace203', 'ace204', 'ace205'])) {
        //     if (Redis::get('day_for_test:' . $userId)) {
        //         error_exit(SysCode::BANK_EXCHANGE_MONEY_ABORT day");
        //     }
        //     $total = static::getTotal($userId);
        //     Log::error("special user:" . $total);
        // }
        $params = static::checkValid($params);
        static::thirdTransferRequest($params);
        $ret = static::localSQLHandle($params);

        // for test
        // $newTotal = static::getTotal($userId);
        // if ($total != $newTotal) {
        //     Log::error("special user error: before:{$total}, after:{$newTotal}, userId:{$userId}, username:" . $userModel['username']);
        //     Redis::set('day_for_test:' . $userId, 1);
        // }
        return $ret;

    }
    // public static $time;
    // public static function printTime($description = "")
    // {
    //     Log::info($description . " use time:" . (static::msectime() - static::$time) . "ms");
    //     static::$time = static::msectime();
    // }
    // public static function msectime()
    // {
    //     list($msec, $sec) = explode(' ', microtime());
    //     return (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    // }
    /**
     * 基础检测
     */
    public static function checkValid($params)
    {
        // static::$time = static::msectime();
        $hallId = $params['hall_id'];
        $userId = $params['user_id'];
        $partnerId = $params['partner_id'];
        $amount = $params['amount'];
        $checkoutAmount = floatval($amount);
        $type = $params['type'];
        $remarkInput = $params['remark'];
        $createAdminId = $params['create_admin_id'];//创建管理员id
        $force = isset($params['force']) ? $params['force'] : false;

        $partner = CheckingService::checkPartnerValid($partnerId, $hallId, $force);
        $partnerName = $partner['partner_name'];

        //MW不支持小数点额度转换， 只支持到元
        if ($partnerName == 'MW') {
            $amount = $params['amount'] = amount_format((int)amount_format($amount), true);
        }

        //检测金额的系统范围
        if (!is_numeric($amount) || empty($checkoutAmount) || $checkoutAmount > MoneyExchangeEnum::MAX_EXCHANGE_AMOUNT || $checkoutAmount <= 0) {
            error_exit(SysCode::BANK_EXCHANGE_AMOUNT_ERROR, ['amount' => $amount]);
        }
        CheckingService::checkHallValid($hallId);
        $user = CheckingService::checkUserValid($userId);
        $username = $user['username'];
        $lockKey = RedisKeys::EXCHANGE_LOCK . $partnerName . ":" . $hallId . ':' . $userId;

        // static::printTime("111");
        // 加锁，一个用户同一时间向同一个API转账只能有一个请求
        usleep(mt_rand(10, 50));
        $is_unlock = Redis::set($lockKey, $amount, 'nx', 'ex', 120);
        if (!$is_unlock) {
            error_exit(SysCode::BANK_EXCHANGE_LOCK);
        }
        $needRollBack = false;
        try {
            //生成资金流水号
            $orderNumber = static::geneRateDealNumber();
            // 同步第三方余额
            $childWallet = static::sysncChildWalletBalance($partnerName, $userId, $username, $orderNumber, $hallId,
                "转账前同步余额");
            // static::printTime("222");
            //主钱包
            $wallet = Money::where(['user_id' => $userId])->first();
            //判断钱包余额是否足够
            $primaryBalance = $wallet['balance'];
            $childBalance = $childWallet['balance'];
            $remark = "";
            if ($type == MoneyExchangeEnum::EXCHANGE_TYPE_M2P) {
                $remark = "主钱包向" . $partnerName . "子钱包转账，转账单号：" . $orderNumber;
                if ($amount > $primaryBalance) {
                    error_exit(SysCode::BANK_EXCHANGE_BALANCE_UNOUGH, [
                        'type' => '主钱包',
                        'balance' => $primaryBalance / CommonEnum::MONEY_RATE,
                        'amount' => $amount / CommonEnum::MONEY_RATE
                    ]);
                }
            } else {
                if ($type == MoneyExchangeEnum::EXCHANGE_TYPE_P2M) {
                    $remark = $partnerName . "子钱包向主钱包转账，转账单号：" . $orderNumber;
                    if ($amount > $childBalance) {
                        error_exit(SysCode::BANK_EXCHANGE_BALANCE_UNOUGH, [
                            'type' => '子钱包',
                            'balance' => $childBalance / CommonEnum::MONEY_RATE,
                            'amount' => $amount / CommonEnum::MONEY_RATE
                        ]);
                    }
                }
            }

            DB::beginTransaction();
            $needRollBack = true;
            $logAmount = $amount;
            // 冻结转账金额，更新余额需要加 >= 0 的判断。主转子的时候需要
            if ($type == MoneyExchangeEnum::EXCHANGE_TYPE_M2P) {
                $logAmount *= -1;
                $ret = static::updateMoney($hallId, $userId, $username, $orderNumber,
                    MoneyExchangeEnum::MONEY_PARTNER_LOG_CATEGORY_SYNC, MoneyLogEnum::USER_TRANSFER_MTOP_LOCK,
                    $logAmount, 0, 0, $createAdminId, "转账开始，冻结金额");
            }

            //写入转账记录
            if ($remarkInput) {
                $remark = $remarkInput . ";" . $remark;
            }
            $transformLog = [
                'user_id' => $userId,
                'username' => $username,
                'money_id' => $wallet['id'],
                'money_partner_id' => $partnerId,
                'money_partner_name' => $partnerName,
                'type' => $type,
                'amount' => $logAmount,
                'order_number' => $orderNumber,
                'operater_admin_id' => $createAdminId,
                'status' => MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_PENNDING,
                'remark' => $remark,
                'hall_id' => $hallId,
            ];
            $insertModel = new MoneyTransfer($transformLog);
            $logParams = [
                'user_id' => $userId,
                'username' => $username,
                'amount' => $logAmount,
                'orderNumber' => $orderNumber,
                'hallId' => $hallId
            ];
            if (!$insertModel->save()) {
                $msg = 'User Exchange Error:' . json_encode($insertModel->getError());
                Log::error($msg);
                static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_FAILED, $msg);
                error_exit(SysCode::BANK_EXCHANGE_TRANSFORM_LOG);
            }

            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_PENNDING, "转账订单初始化");
            DB::commit();
            // static::printTime("333");
        } catch (CustomException $e) {
            Redis::del($lockKey);
            if ($needRollBack) {
                DB::rollBack();
            }
            throw $e;
        } catch (\Exception $e) {
            // 还未转账，可以解锁
            Redis::del($lockKey);
            $msg = sprintf("code: %s, msg:%s", $e->getCode(), $e->getTraceAsString());
            Log::error($msg);
            if ($needRollBack) {
                DB::rollBack();
            }
            error_exit(SysCode::BANK_EXCHANGE_MONEY_FAILED);
        }
        $params['partner'] = $partner;
        $params['username'] = $username;
        $params['orderNumber'] = $orderNumber;
        $params['logParams'] = $logParams;
        $params['remark'] = $remark;
        $params['lockKey'] = $lockKey;
        // static::printTime("444");
        return $params;
    }

    /**
     * 向第三方转账处理
     */
    public static function thirdTransferRequest($params)
    {
        // static::printTime("555");
        $hallId = $params['hall_id'];
        $partner = $params['partner'];
        $userId = $params['user_id'];
        $username = $params['username'];
        $orderNumber = $params['orderNumber'];
        $amount = $params['amount'];
        $logParams = $params['logParams'];
        $type = $params['type'];
        $lockKey = $params['lockKey'];
        $partnerName = strtolower($partner['partner_name']);
        static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_TRANS_READY, "准备向第三方转账");
        try {
            $partnerUsername = self::getPartnerUsername($userId, $username, $partnerName, $hallId);
            $beginTime = time();
            // static::printTime("666");
            // 调用第三方转账接口
            $ret = static::transfer($userId, $username, $partnerName, $orderNumber,
                $type == MoneyExchangeEnum::EXCHANGE_TYPE_M2P ? $amount : -$amount, $hallId);
            // static::printTime("777");
            if (!$ret) {
                Log::info(sprintf("根据转账订单号检测转账结果：开始 user_id:%s, username:%s, order_number:%s", $userId, $username,
                    $orderNumber));
                // 根据转账订单号检测转账结果
                $ret = static::checkResultByOrder($partnerName, $partnerUsername, $orderNumber, $params, $hallId);
                // static::printTime("888");
            }
            if (!$ret || $ret['code'] != SysCode::OK) {
                $msg = sprintf("根据转账订单号检测转账结果：失败! %s, code: %s, msg: %s, user_id:%s, username:%s, order_number:%s",
                    $partnerName, $ret['code'], $ret['msg'] ?? "", $userId, $username, $orderNumber);
                Log::info($msg);
                static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_TRANS_TRY, $msg);
                $msg = sprintf("根据会员名+时间段检测转账结果：开始 %s, user_id:%s, username:%s, order_number:%s", $partnerName,
                    $userId, $username, $orderNumber);
                Log::info($msg);
                // 根据会员名+时间段检测转账结果
                $ret = static::checkResultByTime($partnerName, $partnerUsername, $beginTime, $params, $hallId);
                // static::printTime("999");
                if ($ret && $ret['code'] == SysCode::API_ERROR_TRANSFER_FAILED) {
                    error_exit(SysCode::BANK_EXCHANGE_GETBALANCE_MONEY_ERROR, "转账失败，请稍后重试");
                }
            }
            if (!$ret || $ret['code'] != SysCode::OK) {
                $msg = sprintf("根据会员名+时间段检测转账结果：失败! %s, code: %s, msg: %s, user_id:%s, username:%s, order_number:%s",
                    $partnerName, $ret['code'], $ret['msg'] ?? "", $userId, $username, $orderNumber);
                Log::info($msg);
                static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_TRANS_TRY, $msg);
                $msg = sprintf("刷新余额检测转账结果：开始 %s, user_id:%s, username:%s, order_number:%s", $partnerName, $userId,
                    $username, $orderNumber);
                Log::info($msg);
                // 刷新余额检测转账结果
                $ret = static::checkBalance($partnerName, $userId, $username, $orderNumber, $amount, $hallId,
                    $logParams);
                // static::printTime("999");
            }
            if (!$ret || $ret['code'] != SysCode::OK) {
                $msg = sprintf("刷新余额检测转账结果：失败! %s, code: %s, msg: %s, user_id:%s, username:%s, order_number:%s",
                    $partnerName, $ret['code'], $ret['msg'] ?? "", $userId, $username, $orderNumber);
                static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_TRANS_TRY, $msg);
                Log::info($msg);
                // todo 告警，通知客服人工排查，加入通用告警机制后实现
            }
            if (!$ret || $ret['code'] != SysCode::OK) {
                $msg = "调用第三方转账失败:" . json_encode($ret);
                error_exit(SysCode::BANK_EXCHANGE_GETBALANCE_MONEY_ERROR, $msg);
            }
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_TRANS_OK, "向第三方转账成功");
            // static::printTime("1010");
        } catch (CustomException $e) {
            //自定义消息返回code+msg
            Redis::del($lockKey);
            // 正常检测到失败，返回就可以了，不用再补单了
            if ($e->getCode() == SysCode::BANK_EXCHANGE_MONEY_FAILED) {
                throw $e;
            }
            $msg = sprintf("向第三方转账失败1, api:%s code: %s, msg: %s", $partnerName, $e->getCode(), $e->getTraceAsString());
            Log::error($msg);
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            throw $e;
        } catch (\Throwable $e) {
            Redis::del($lockKey);
            $msg = sprintf("向第三方转账失败2, api:%s, code: %s, msg: %s", $partnerName, $e->getCode(), $e->getTraceAsString());
            Log::error($msg);
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            error_exit(SysCode::BANK_EXCHANGE_MONEY_ABORT);
        }
    }

    public static function handleTransferError(
        $type,
        $userId,
        $lockKey,
        $amount,
        $logParams,
        $hallId,
        $username,
        $orderNumber,
        $remark=''
    ) {
        Redis::del($lockKey);
        DB::beginTransaction();
        try {
            // 解冻金额。主转子的时候
            if ($type == MoneyExchangeEnum::EXCHANGE_TYPE_M2P) {
                static::updateMoney($hallId, $userId, $username, $orderNumber,
                    MoneyExchangeEnum::MONEY_PARTNER_LOG_CATEGORY_SYNC, MoneyLogEnum::USER_TRANSFER_MTOP_LOCK_REVERT,
                    $amount, 0, 0, $createAdminId ?? 0, "转账失败，解冻金额", false);
            }
            //修改订单的状态
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_FAILED, "转账正常返回失败，请重新转账");
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_FAILED,
                !empty($remark) ? $remark : "转账正常返回失败，请重新转账");
            DB::commit();
        } catch (\Exception $e) {
            Log::error("转账-正常失败处理出错：" , [$e->getCode(), $e->getMessage()]);
            DB::rollBack();
            error_exit($e->getCode(), $e->getMessage());
        }
    }

    /**
     * 转账之后，本地钱包日志处理
     */
    public static function localSQLHandle($params)
    {
        $hallId = $params['hall_id'];
        $userId = $params['user_id'];
        $username = $params['username'];
        $amount = $params['amount'];
        $type = $params['type'];
        $createAdminId = $params['create_admin_id'];//创建管理员id
        $remark = $params['remark'];
        $logParams = $params['logParams'];
        $orderNumber = $params['orderNumber'];
        $partner = $params['partner'];
        $partnerName = $partner['partner_name'];
        $lockKey = $params['lockKey'];
        $params['is_manual'] = $params['is_manual'] ?? 0;//是否人工存提， 默认否

        //开启事务 钱包相关操作
        DB::beginTransaction();
        try {
            $logAmount = $amount;
            $category = MoneyExchangeEnum::MONEY_PARTNER_LOG_CATEGORY_SYNC;
            // 解冻金额。主转子的时候
            if ($type == MoneyExchangeEnum::EXCHANGE_TYPE_M2P) {
                //厅主后台人工存提 is_manual == 1
                $moneyLogType = ($params['is_manual'] == 1 ? MoneyLogEnum::USER_TRANSFER_MTOP_MANUAL : MoneyLogEnum::USER_TRANSFER_MTOP);
                $logAmount = -1 * $amount;
                static::updateMoney($hallId, $userId, $username, $orderNumber,
                    $category, MoneyLogEnum::USER_TRANSFER_MTOP_UNLOCK,
                    -$amount, 0, 0, $createAdminId, "转账成功，扣除解冻金额", false);
                $money = Money::where(['user_id' => $userId])->first();
                // 正常转账日志记录
                static::addMoneyLog($hallId, $userId, $username, $orderNumber,
                    $category, $moneyLogType, $logAmount, $money['balance'], $money['balance'] - $logAmount,
                    $createAdminId, $remark);
            } else {
                $moneyLogType = ($params['is_manual'] == 1 ? MoneyLogEnum::USER_TRANSFER_PTOM_MANUAL : MoneyLogEnum::USER_TRANSFER_PTOM);
                // 子转主才要加钱，因为主转子已经扣过了，冻结金额
                static::updateMoney($hallId, $userId, $username, $orderNumber,
                    $category, $moneyLogType, $amount, 0, 0, $createAdminId, $remark, false);
            }
            //修改订单的状态
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_SUCCESS, "转账结束");
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_SUCCESS, "转账成功");
            DB::commit();
            // static::printTime("a222");
            return ['order_number' => $orderNumber];
        } catch (CustomException $e) {
            DB::rollBack();
            //自定义消息返回code+msg
            $msg = sprintf("本地转账失败1：code: %s, msg:%s", $e->getCode(), $e->getTraceAsString());
            Log::error($msg);
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            throw $e;
        } catch (\Throwable $e) {
            DB::rollBack();
            $msg = sprintf("本地转账失败2：code: %s, msg:%s", $e->getCode(), $e->getTraceAsString());
            Log::error($msg);
            static::addTransformLog($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            static::updateTranferLogstatus($logParams, MoneyExchangeEnum::MONEY_TRANSFORM_STATUS_ABORT, $msg);
            error_exit(SysCode::BANK_EXCHANGE_MONEY_ABORT);
        } finally {
            // 删除转账锁
            Redis::del($lockKey);
            static::sysncChildWalletBalance($partnerName, $userId, $username, $orderNumber, $hallId, "转账后同步余额");
            // static::printTime("a333");
        }
    }

    /**
     * 修改转账订单状态
     * @param $userId
     * @param $tradeNo
     * @param $status
     * @return int
     * @throws yii\db\Exception
     */
    public static function updateTranferLogstatus($logParams, $status, $remark)
    {
        $updateMoneyTransform = MoneyTransfer::where([
            'user_id' => $logParams['user_id'],
            'order_number' => $logParams['orderNumber'],
            'hall_id' => $logParams['hallId']
        ]);
        $update = [
            'status' => $status,
            'remark' => $remark,
        ];
        $rel = $updateMoneyTransform->update($update);
        if (empty($rel)) {
            $msg = "写入 money_transfer 日志失败:" . json_encode($logParams);
            Log::error($msg);
            error_exit(SysCode::FAILED, $msg);
        }
    }


    /**
     *  记录额度转化过程中的log
     */
    public static function addTransformLog($logParams, $status, $remark)
    {
        $transformData = [
            'user_id' => $logParams['user_id'],
            'username' => $logParams['username'],
            'order_number' => $logParams['orderNumber'],
            'amount' => $logParams['amount'],
            'hall_id' => $logParams['hallId'],
            'status' => $status,
            'remark' => $remark
        ];
        $insertModel = new MoneyTransferLog($transformData);
        $ret = $insertModel->save();
        if (empty($ret)) {
            $msg = "写入 money_transfer_log 日志失败:" . json_encode($transformData);
            Log::error($msg);
            error_exit(SysCode::FAILED, $msg);
        }
    }

    /**资金流水号
     * @param int $rand
     * @param int $length
     * @return string
     */
    public static function geneRateDealNumber($rand = 99999, $length = 5)
    {
        return generate_order_number();
    }


    // 获取用户名
    public static function getPartnerUsername($userId, $username, $partnerName, $hall_id)
    {
        $pu = PartnerUser::where([
            'user_id' => $userId,
            'partner_name' => strtoupper($partnerName),
            'hall_id' => $hall_id
        ])->first();
        if ($pu) {
            return $pu->partner_username;
        }
        return static::createUser($userId, $username, $partnerName, $hall_id);
    }

    // 创建用户
    public static function createUser($userId, $username, $partnerName, $hallId)
    {
        $partnerUsername = strtolower(env('APP_ENV') . "_" . $partnerName . "_" . $hallId . "_" . $userId);

        $partnerName = strtoupper($partnerName);
        //兼容第三方用户OG,MG 不支持下划线的用户名
        if (in_array($partnerName, ['OG', 'MG'])) {
            $partnerUsername = str_replace("_", "X", $partnerUsername);
        }
        // 检测第三方是否有效
        $partner = HallPartnerConfig::where(['partner_name' => $partnerName])->first();
        if (empty($partner)) {
            error_exit(SysCode::BANK_EXCHANGE_PARTNER_ERROR, $partnerName);
        }
        // 检测子钱包是否存在
        $checkChildWallet = MoneyPartner::where([
            'user_id' => $userId,
            'partner_id' => $partner['partner_id']
        ])->first();
        if (empty($checkChildWallet)) {
            $MoneyModel = MoneyPartner::insert([
                'user_id' => $userId,
                'partner_id' => $partner['partner_id'],
                'partner_name' => $partnerName,
                'balance' => 0,
                'status' => 1,
                'hall_id' => $hallId,
            ]);
            if (empty($MoneyModel)) {
                error_exit(SysCode::GAME_THIRD_MONEY_PARTNER_ERROR);
            }
        }

        $checkPartnerName = PartnerUser::where(['user_id' => $userId, 'partner_id' => $partner['partner_id']])->first();
        if (empty($checkPartnerName)) {
            // 向第三方请求注册
            $registerResult = self::register($partnerUsername, $partnerName);
            if (isset($registerResult['code'])
                && $registerResult['code'] != SysCode::OK
                && $registerResult['code'] != SysCode::API_ERROR_USER_EXIST) {
                Log::error($registerResult['code'] . " 创建用户失败:" . $userId . ", partner:" . $partnerName . ", hall: " . $hallId);
                error_exit(SysCode::GAME_THIRD_GAME_ACCOUNT_ERROR);
            }

            $partnerUserModel = PartnerUser::insert([
                'user_id' => $userId,
                'username' => $username,
                'partner_username' => $partnerUsername,
                'partner_id' => $partner['partner_id'],
                'partner_name' => $partnerName,
                'partner_password' => 123456,
                'hall_id' => $hallId,
            ]);
            if (empty($partnerUserModel)) {
                error_exit(SysCode::GAME_THIRD_MONEY_PARTNER_USER_ERROR);
            }
        }
        return $username;
    }

    // 转账
    public static function transfer($userId, $username, $partnerName, $orderNumber, $amount, $hallId)
    {
        $partnerUsername = self::getPartnerUsername($userId, $username, $partnerName, $hallId);
        $data = [
            'api_name' => $partnerName,
            'username' => $partnerUsername,
            'transaction_no' => $orderNumber,
            'amount' => $amount,
            'password' => 123456,
        ];
        $ret = self::partnerRequest('/api/transfer', 'POST', $data);
        if (!isset($ret['code']) || $ret['code'] != SysCode::OK) {
            return false;
        }
        return true;
    }

    // 第三方注册
    public static function register($username, $partnerName)
    {
        $data = [
            'api_name' => $partnerName,
            'username' => $username,
            'password' => '123456',
            'is_trial' => 0,
        ];
        return self::partnerRequest('/api/register', 'POST', $data);
    }

    // 获取余额
    public static function getBalance($userId, $username, $partnerName, $hallId)
    {
        $partnerUsername = self::getPartnerUsername($userId, $username, $partnerName, $hallId);
        $data = [
            'api_name' => $partnerName,
            'username' => $partnerUsername,
            'password' => 123456
        ];
        return self::partnerRequest('/api/getBalance', 'GET', $data);
    }


    // Partner 第三方交互请求
    public static function partnerRequest($url, $method, $data)
    {
        $baseUrl = config('app.rpcserver');
        $url = rtrim($baseUrl, '/') . '/' . ltrim($url, '/');
        $resp = request($url, $method, $data);
        $ret = $resp->getBody()->getContents();
        $ret = json_decode($ret, true);

        if (!isset($ret['code'])) {
            $ret['code'] = -1;
        }
        // 记录失败LOG
        if ($resp->getStatusCode() != 200 || !isset($ret['code']) || $ret['code'] != SysCode::OK) {
            Log::error("patner Request Error: ", [
                'url' => $url,
                'method' => $method,
                'data' => $data,
                'status' => $resp->getStatusCode(),
                'ret' => $ret
            ]);
        }
        return $ret;
    }

    /**
     * 同步第三方余额
     * @param $partnerName string 游戏类型
     * @param $lastBalance int 上次余额
     * @param $childWalletId  int 钱包id
     * @param $userId int  用户id
     */
    public static function sysncChildWalletBalance(
        $partnerName,
        $userId,
        $username,
        $orderNumber,
        $hallId,
        $remark = ""
    ) {
        $partnerName = strtoupper($partnerName);
        //检测并创建子钱包和第三方账号(第三方登陆或者额度转化时候检测账号和子钱包)
        static::createUser($userId, $username, $partnerName, $hallId);

        $ret = static::getBalance($userId, $username, $partnerName, $hallId);
        if (isset($ret['code']) && $ret['code'] != 0) {
            Log::error("同步{$partnerName}子钱包失败:" . json_encode($ret));
            error_exit(SysCode::BANK_EXCHANGE_GETBALANCE_ERROR);
        }
        $balance = $ret['data']['balance'];

        $mp = MoneyPartner::where([
            'user_id' => $userId,
            'partner_name' => $partnerName,
        ])->first();
        $beforeBalance = $mp['balance'];

        if (empty($mp)) {
            Log::error('未找到对应的子钱包:', ['user_id' => $userId, 'partner_name' => $partnerName]);
            error_exit(SysCode::BANK_EXCHANGE_WALLET_NOT_EXIT);
        }
        // 余额相同，不需要继续处理
        if ($beforeBalance == $balance) {
            return $mp;
        }

        DB::beginTransaction();
        try {
            // 强制以第三方API获取到的余额为准
            $mp->update(['balance' => $balance]);
            MoneyPartnerLog::insert([
                'money_partner_id' => $mp['id'],
                'user_id' => $userId,
                'category' => MoneyExchangeEnum::MONEY_PARTNER_LOG_CATEGORY_SYNC,
                'balance' => $balance,
                'balance_before' => $beforeBalance,
                'amount' => $balance - $beforeBalance,
                'order_number' => $orderNumber,
                'hall_id' => $hallId,
                'remark' => $remark
            ]);
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error('updateMoneyParter Error:' . $e->getMessage());
            error_exit(SysCode::BANK_EXCHANGE_SQL_ERROR);
        }
        return $mp;
    }

    //获取用户的余额入口
    public static function getUserBalance($params)
    {
        $userId = $params['user_id'];
        $hall_id = $params['hall_id'];
        $partnerName = $params['api_name'];
        $user = User::where(['id' => $userId])->first();
        return static::getBalance($userId, $user['username'], $partnerName, $hall_id);
    }

    // 同步用户子钱包
    public static function sysncUserChildWalletBalance($params)
    {
        $userId = $params['user_id'];
        $hall_id = $params['hall_id'];
        $partnerName = $params['api_name'];
        $user = User::where(['id' => $userId])->first();
        //生成资金流水号
        $orderNumber = static::geneRateDealNumber();
        //同步第三方余额
        return static::sysncChildWalletBalance($partnerName, $userId, $user['username'], $orderNumber, $hall_id);
    }

    /**
     * 根据转账订单号检测转账结果
     */
    public static function checkResultByOrder($partnerName, $partnerUsername, $orderNumber, $params, $hallId)
    {
        $data = [
            'api_name' => $partnerName,
            'username' => $partnerUsername,
            'order_number' => $orderNumber
        ];
        $ret = self::partnerRequest('/api/checkTransferResult', 'get', $data);
        return static::handleResult($ret, $params, $hallId);
    }

    /**
     * 根据转账会员账号+时间段检测转账结果
     */
    public static function checkResultByTime($partnerName, $partnerUsername, $beginTime, $params, $hallId)
    {
        $data = [
            'api_name' => $partnerName,
            'username' => $partnerUsername,
            'begin_time' => $beginTime,
            'end_time' => $beginTime + 5 * 60
        ];
        $ret = self::partnerRequest('/api/getTransferResult', 'get', $data);
        if ($ret && $ret['code'] == SysCode::OK) {
            // todo 检测转账有效性
            /**
             * {
             *     username:"",//第三方用户名
             *     balance:"",//当前余额（单位厘）
             *     amount:"",//转账金额（单位厘，正数）
             *     type:"",//1:子钱包加钱，2:子钱包减钱
             *     status:"",//1:成功, 2:失败, 3:其他
             *     created_at:"",//转账发生时间
             * }
             */
            if (empty($ret['data'])) {
                Log::info("获取的转账数据为空");
                $ret['code'] = SysCode::API_ERROR_TRANSFER_FAILED;
            }
        }
        return static::handleResult($ret, $params, $hallId);
    }

    public static function handleResult($ret, $params, $hallId)
    {
        $userId = $params['user_id'];
        $username = $params['username'];
        $orderNumber = $params['orderNumber'];
        $amount = $params['amount'];
        $logParams = $params['logParams'];
        $type = $params['type'];
        $lockKey = $params['lockKey'];
        if (!isset($ret['code'])) {
            return false;
        }
        if ($ret['code'] == SysCode::API_ERROR_TRANSFER_FAILED) {
            // 正常失败流程
            static::handleTransferError($type, $userId, $lockKey, $amount, $logParams, $hallId, $username,
                $orderNumber);
            error_exit(SysCode::BANK_EXCHANGE_MONEY_FAILED, "转账失败，请稍后重试");
        } elseif ($ret['code'] == SysCode::OK) {
            Log::info("根据转账订单号检测转账结果：成功! user_id:{$userId}, username:{$username}, order_number:{$orderNumber}");
        }
        return $ret;
    }

    /**
     * 刷新余额检测转账结果
     */
    public static function checkBalance($partnerName, $userId, $username, $orderNumber, $amount, $hallId, $logParams)
    {
        $childWallet = MoneyPartner::where(['user_id' => $userId, 'partner_name' => $partnerName])->first();
        $beforeBalance = $childWallet['balance'];
        $childWallet = static::sysncChildWalletBalance($partnerName, $userId, $username, $orderNumber, $hallId,
            "刷新余额检测转账结果");
        $currentBalance = $childWallet['balance'];
        Log::info(sprintf("余额对比：%s, 目前：%s，查询：%s", $partnerName, $beforeBalance, $currentBalance));
        if (abs(abs($currentBalance - $beforeBalance) - $amount) <= 0.0001) {
            // 转账成功
            Log::info(sprintf("刷新余额检测转账结果：成功! %s, user_id:%s, username:%s, order_number:%s", $partnerName, $userId,
                $username, $orderNumber));
            return true;
        }
        return false;
    }

    /**
     * 处理第三方转账单据
     * @param $params
     * @throws CustomException
     * @throws \common\exception\CustomException
     */
    public static function handleTransfer($params)
    {
        //获取转账信息
        $moneyTransferInfo = MoneyTransfer::where(['id' => (int)$params['money_transfer_id']])->first();
        if (!$moneyTransferInfo) {
            error_exit(SysCode::BANK_EXCHANGE_HANDLE_ORDER_NOT_FOUND);
        }
        //不能处理已成功转账单
        if ($moneyTransferInfo['status'] == MoneyTransferEnum::TRANSFER_STATUS_SUCCESS) {
            error_exit(SysCode::BANK_EXCHANGE_HANDLE_STATUS_SHOULD_NOT_SUCC);
        }

        //转账日志
        $logParams = [
            'user_id' => $moneyTransferInfo['user_id'],
            'username' => $moneyTransferInfo['username'],
            'amount' => $moneyTransferInfo['amount'],
            'orderNumber' => $moneyTransferInfo['order_number'],
            'hallId' => $moneyTransferInfo['hall_id']
        ];
        $type = $moneyTransferInfo['type'];
        $amount = abs($moneyTransferInfo['amount']);//主转子需要转换为正数

        if($params['status'] == MoneyTransferEnum::TRANSFER_STATUS_SUCCESS ){
            //转账成功处理
            $handleParams = [
                'hall_id' => $moneyTransferInfo['hall_id'],
                'user_id' => $moneyTransferInfo['user_id'],
                'username' => $moneyTransferInfo['username'],
                'amount' => $amount,
                'type' => $type,
                'create_admin_id' => $moneyTransferInfo['operater_admin_id'],
                'remark' => !empty($params['remark']) ? $params['remark'] : '处理转账成功',
                'logParams' => $logParams,
                'orderNumber' => $moneyTransferInfo['order_number'],
                'partner' => ['partner_name' => $moneyTransferInfo['money_partner_name']],
                'lockKey' => '',
            ];
            return static::localSQLHandle($handleParams);
        }else{
            //转账失败处理 使用正常失败流程
            static::handleTransferError($type, $moneyTransferInfo['user_id'], '', $amount, $logParams, $moneyTransferInfo['hall_id'], $moneyTransferInfo['username'],
                $moneyTransferInfo['order_number'], $params['remark'] ?? '');
            return ['order_number' => $moneyTransferInfo['order_number']];
        }
    }
}